<?php
/**
 * 基类
 *
 * @author Ferman Yee <fermanyee@163.com>
 * @copyright 2018-11-6
 */

namespace XppOpenApi\Core;

use XppOpenApi\Util\HttpClient;
use XppOpenApi\Util\Util;

class ApiBase {

    protected $baseUrl = 'http://api.china.io';
    
    /**
     * 获取access token url
     * @var string
     */
    protected $accessTokenUrl = 'http://api.china.io/oauth/2.0/token';

    /**
     * appId
     * @var string
     */
    protected $appId = '';

    /**
     * appSecret
     * @var string
     */
    protected $appSecret = '';
    
    /**
     * @param string $appId 
     * @param string $appSecret
     */
    public function __construct($appId, $appSecret) {
        $this->appId = trim($appId);
        $this->appSecret = trim($appSecret);
        $this->client = new HttpClient();
        $this->version = '1.0';
        $this->proxies = array();
    }
    
    /**
     * 获取接口URL
     * @param string $api
     * @param array $params
     * @param string $baseUrl
     * @throws \Exception
     * @return string
     */
    protected function getApiUrl($module, $api, $params = array(), $version = 'V1', $baseUrl = '') {
        if (!$api) {
            throw new \Exception('invalid api');
        }
        
        if (!$baseUrl) {
            $baseUrl = $this->baseUrl;
        }
        
        return $baseUrl . '/' . $module . '/' . $version . $api . ($params ? '?' . http_build_query($params) : '');
    }

    /**
     * 查看版本
     * @return string
     */
    public function getVersion() {
        return $this->version;
    }

    /**
     * 连接超时
     * @param int $ms 毫秒
     */
    public function setConnectionTimeoutInMillis($ms) {
        $this->client->setConnectionTimeoutInMillis($ms);
    }

    /**
     * 响应超时
     * @param int $ms 毫秒
     */
    public function setTimeoutInMillis($ms) {
        $this->client->setTimeoutInMillis($ms);
    }

    /**
     * 处理请求参数
     * @param  string $url
     * @param array $params
     * @param array $data
     * @param array $headers
     */
    protected function proccessRequest($url, &$params, &$data, $headers) {
        $params['apiSdk'] = 'php';
        $params['apiSdkVersion'] = $this->version;
    }

    /**
     * Api 请求
     * @param  string $url
     * @param  mixed $data
     * @return mixed
     */
    protected function request($url, $data = array(), $headers = array()) {
        try {
            $result = $this->validate($url, $data);
            if ($result !== true) {
                return $result;
            }

            $authObj = $this->auth();
            
            $params = array();
            $params['access_token'] = $authObj['access_token'];
            
            $this->proccessRequest($url, $params, $data, $headers);
            
            $params['app_id'] = $this->appId;
            $params['timestamp'] = time();
            $params['nonce'] = uniqid();
            
            $params['sign'] = Util::sign($params, $data);
            
            $response = $this->client->post($url, $data, $params, $headers);
            
            $obj = $this->proccessResult($response['content']);
            
            if (isset($obj['status']) && $obj['status'] == 40003) {
                $authObj = $this->auth(true);
                $params['access_token'] = $authObj['access_token'];
                $params['sign'] = Util::sign($params, $data);
                $response = $this->client->post($url, $data, $params, $headers);
                $obj = $this->proccessResult($response['content']);
            }

            if (empty($obj) || $obj['status'] == 200) {
                $this->writeAuth($authObj);
            }
        } catch(\Exception $e) {
            if ($e->getCode() != 0) {
                return array(
                    'status' => $e->getCode(),
                    'info' => $e->getMessage(),
                    'data' => array(),
                );
            } else {
                return array(
                    'status' => 0,
                    'info' => 'connection or read data timeout',
                    'data' => array(),
                );
            }
        }

        return $obj;
    }
    
    /**
     * 格式检查
     * @param  string $url
     * @param  array $data
     * @return mixed
     */
    protected function validate($url, &$data) {
        return true;
    }

    /**
     * 格式化结果
     * @param $content string
     * @return mixed
     */
    protected function proccessResult($content) {
        return json_decode($content, true);
    }

    /**
     * 返回 access token 路径
     * @return string
     */
    private function getAuthFilePath() {
        return dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . md5($this->appId);
    }

    /**
     * 写入本地文件
     * @param  array $obj
     * @return void
     */
    private function writeAuth($obj) {
        if ($obj === null || (isset($obj['is_read']) && $obj['is_read'] === true)) {
            return;
        }

        $obj['time'] = time();
        file_put_contents($this->getAuthFilePath(), json_encode($obj));
    }

    /**
     * 读取本地缓存
     * @return array
     */
    private function readAuth() {
        $content = @file_get_contents($this->getAuthFilePath());
        if ($content !== false) {
            $obj = json_decode($content, true);
            $obj['is_read'] = true;
            if ($obj['time'] + $obj['expires_in'] - 30 > time()) {
                return $obj;
            }
        }

        return null;
    }

    /**
     * 认证
     * @param bool $refresh 是否刷新
     * @return array
     */
    private function auth($refresh = false) {
        if (!$refresh) {
            $obj = $this->readAuth();
            if (!empty($obj)) {
                return $obj;
            }
        }

        $response = $this->client->get($this->accessTokenUrl, array(
            'grant_type' => 'client_credentials',
            'app_id' => $this->appId,
            'app_secret' => $this->appSecret,
        ));
        
        $obj = json_decode($response['content'], true);
        if ($obj['status'] != 200 && $obj['status'] != 40003) {
            throw new \Exception($obj['info'], $obj['status']);
        } else {
            $obj = $obj['data'];
        }

        return $obj;
    }
    
    public function getInfo() {
        $info = $this->client->getInfo();
        $info && $info['response'] = json_decode($info['response'], true);
        return $info;
    }
}
